/*
** LuaRover implementation
** See Copyright Notice in lua_rover.hpp
*/

#include "lua_rover.hpp"
extern "C" {
#include "../lua_5_1_3/lua.h"
#include "../lua_5_1_3/lualib.h"
#include "../lua_5_1_3/lauxlib.h"
};
#include <string>
#include <iostream>
#include <sstream>
#include <vector>

#pragma warning(disable:4290)
#pragma warning(disable:4018)
#pragma warning(disable:4146)
#pragma warning(disable:4267)
#pragma warning(disable:4244)
#pragma warning(disable:4800)

namespace LuaRover
{
	using std::string;
	using std::vector;
	using std::istringstream;

/**********************************************************

    Helpers

**********************************************************/

    lua_State* init_lua()
	{
		lua_State* state = luaL_newstate();
		luaL_openlibs(state);
		return state;
	}

	void close_lua(lua_State* state)
	{
		if(state)
			lua_close(state);
	}

	void error(lua_State* lua_state,
		const string additional_info = string())
	{
		string err_msg = lua_tostring(lua_state, -1);
		lua_pop(lua_state, 1);
		throw LuaError(LuaError::LUA_GENERIC_ERROR, additional_info + err_msg);
	}

	void run_script(lua_State* lua_state, const char* filename)
	{
		if(luaL_loadfile(lua_state, filename)
			|| lua_pcall(lua_state, 0, LUA_MULTRET, 0))
 		{
			error(lua_state);
		}
	}

	// adapted from
	// http://www.digitalpeer.com/id/simple
	vector<string> get_field_names(const string& names)
	{
		vector<string> fields;
		string delim(".");
		string::size_type last_pos = 0;
		string::size_type pos = 0;

		bool true_flag = true;

		while(true_flag)
		{
			pos = names.find_first_of(delim, last_pos);
			if( pos == string::npos )
			{
				fields.push_back(names.substr(last_pos));
				break;
			}
			else
			{
				fields.push_back(names.substr(last_pos, pos - last_pos));
				last_pos = pos + 1;
			}
		}
		return fields;
	}
	
	bool to_integer(const string& str, int& i)
	{
		istringstream istr(str);
		if (!(istr >> i))
		{
			return false;
		}
		return true;
	}

/**********************************************************

     class LuaRover::LuaError implementation

**********************************************************/

	LuaError::LuaError(ErrorCode error_code, string msg)
	: error_msg_(msg), error_code_(error_code)
	{}

	const char* LuaError::what() { return error_msg_.c_str(); }

/**********************************************************

     class LuaRover::LuaGlobal implementation

**********************************************************/
	LuaGlobal::LuaGlobal(const char* filename) throw(LuaError)
		: lua_state_(init_lua())
		, filename_(filename?filename:"")
		, lua_run_(false)
	{
		//run_script(lua_state_, filename);
	}

	LuaGlobal::LuaGlobal(const LuaGlobal& rhs)
		: lua_state_(rhs.lua_state_)
	{
		rhs.lua_state_ = 0;
	}

	LuaGlobal::~LuaGlobal()
	{
		close_lua(lua_state_);
	}
	
	LuaObject LuaGlobal::operator[](const char* name) throw(LuaError)
	{
		return LuaObject(lua_state_, name);
	}

	LuaObject LuaGlobal::get(const char* name) throw(LuaError)
	{
		if(!lua_run_)
		{
			run_script(lua_state_, filename_.c_str());
			lua_run_ = true;
		}

		LuaObject lo(lua_state_);
		const vector<string>& fields = get_field_names(name);
		if (!fields[0].empty())
		{
			lua_getglobal(lua_state_, "_G");
		    lua_getfield(lua_state_, -1, fields[0].c_str());
			lo.base_stack_count_ = 2;
			lo.last_key_ = fields[0];
			if (lua_isnil(lua_state_, -1))
		    	throw LuaError(LuaError::LUA_GENERIC_ERROR,
					fields[0] + " is Not Defined");
		}

		lo.stack_count_ = 0;
		for(int i = 1; i < fields.size(); i++)
		{
			int index;
			if (to_integer(fields[i], index))
			{
				lo[index];
			}
			else
			{
				lo[fields[i].c_str()];
			}
			lo.base_stack_count_ += lo.stack_count_;
			lo.stack_count_ = 0;
		}
		return lo;
	}

/**********************************************************

     class LuaRover::LuaObject implementation
 
**********************************************************/
	LuaObject::LuaObject(lua_State* state)
	    : lua_state_(state), base_stack_count_(0)
		, stack_count_(0), global_name_(), last_key_()
	{}
	
	LuaObject::LuaObject(lua_State* state, const char* name) throw(LuaError)
	    : lua_state_(state), stack_count_(0)
		, global_name_(name)
	    , last_key_(name)
	{
		lua_getglobal(lua_state_, "_G");
	    lua_getfield(lua_state_, -1, name);
/*
		if (lua_isnil(lua_state_, -1))
		    throw LuaError(LuaError::LUA_GENERIC_ERROR,
			global_name_ + " is Not Defined");
*/
		base_stack_count_ = 2;
	}

	LuaObject::LuaObject(const LuaObject& rhs)
		: lua_state_(rhs.lua_state_)
		, base_stack_count_(rhs.base_stack_count_ + rhs.stack_count_)
		, stack_count_(0)
		, global_name_(rhs.global_name_)
		, last_key_(rhs.last_key_)
	{
		rhs.base_stack_count_ = 0;
	}

	LuaObject::~LuaObject()
	{
    	lua_pop(lua_state_, base_stack_count_);
	}
	
	LuaObject& LuaObject::operator[](const char* name) throw(LuaError)
	{
       	if (!lua_istable(lua_state_, -1))
		    throw LuaError(LuaError::NOT_A_TABLE,
				last_key_ + " is Not A Table");

	    lua_getfield(lua_state_, -1, name);
		++stack_count_;
		last_key_ = name;
	    return *this;
	}

	LuaObject& LuaObject::operator[](int index) throw(LuaError)
	{
       	if (!lua_istable(lua_state_, -1))
		    throw LuaError(LuaError::NOT_A_TABLE,
				last_key_ + " is Not A Table");

		lua_pushinteger(lua_state_, index);
		lua_gettable(lua_state_, -2);

		std::stringstream s;
		s << index;
		last_key_ = s.str();
		++stack_count_;
	    return *this;
	}

	template<>
	int LuaObject::val<int>() throw(LuaError)
	{
		if (!lua_isnumber(lua_state_, -1))
	    	throw LuaError(LuaError::NOT_A_NUMBER,
				last_key_ + " is Not A Number");

		int result = lua_tointeger(lua_state_, -1);
		pop_stack();
		return result;
	}
	
	template<>
	string LuaObject::val<string>() throw(LuaError)
	{
		if (!lua_isstring(lua_state_, -1))
	    	throw LuaError(LuaError::NOT_A_STRING,
				last_key_ + " is Not A String");

		string result = lua_tostring(lua_state_, -1);
		pop_stack();
		return result;
	}

	template<>
	double LuaObject::val<double>() throw(LuaError)
	{
		if (!lua_isnumber(lua_state_, -1))
	    	throw LuaError(LuaError::NOT_A_NUMBER,
				last_key_ + " is Not A Number");

		double result = lua_tonumber(lua_state_, -1);
		pop_stack();
		return result;
	}

	LuaObject::operator int()
	{
		return val<int>();
	}
	
	LuaObject::operator string()
	{
		return val<string>();
	}
	
	LuaObject::operator double()
	{
		return val<double>();
	}
	
	void LuaObject::operator=(int val)
	{
		lua_pushinteger(lua_state_, val);
		lua_setfield(lua_state_, -3, last_key_.c_str());
		pop_stack();
	}

	void LuaObject::operator=(double val)
	{
		lua_pushnumber(lua_state_, val);
		lua_setfield(lua_state_, -3, last_key_.c_str());
		pop_stack();
	}

	void LuaObject::operator=(std::string val)
	{
		lua_pushstring(lua_state_, val.c_str());
		lua_setfield(lua_state_, -3, last_key_.c_str());
		pop_stack();
	}

	size_t LuaObject::length()
	{
		size_t len = lua_objlen(lua_state_, -1);
		pop_stack();
		return len;
	}

	bool LuaObject::isnil()
	{
		bool result = lua_isnil(lua_state_, -1);
		pop_stack();
		return result;
	}

	bool LuaObject::isnumber()
	{
		bool result = lua_isnumber(lua_state_, -1);
		pop_stack();
		return result;
	}

	bool LuaObject::isstring()
	{
		bool result = lua_isstring(lua_state_, -1);
		pop_stack();
		return result;
	}

	bool LuaObject::istable()
	{
		bool result = lua_istable(lua_state_, -1);
		pop_stack();
		return result;
	}

	int LuaObject::or_val(int value)
	{
        if (lua_isnil(lua_state_, -1))
		{
            pop_stack();
			return value;
		}
		else
		    return val<int>();
	}

	double LuaObject::or_val(double value)
	{
        if (lua_isnil(lua_state_, -1))
		{
            pop_stack();
			return value;
		}
		else
		    return val<double>();
	}

	string LuaObject::or_val(const string& value)
	{
        if (lua_isnil(lua_state_, -1))
		{
            pop_stack();
			return value;
		}
		else
		    return val<string>();
	}

	void LuaObject::pop_stack()
	{
        lua_pop(lua_state_, stack_count_);
        last_key_ = global_name_;
        stack_count_ = 0;
	}
}

#pragma warning(default:4290)
#pragma warning(default:4018)
#pragma warning(default:4146)
#pragma warning(default:4267)
#pragma warning(default:4244)
#pragma warning(default:4800)